//--------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <ctype.h>
#include <util/delay.h>

#include "dkbasic.h"
#include "dkterm.h"
#include "dkfile_ps2kbd.h"
#include "dkstring.h"
//--------------------------------------------------------------------


// NOTE: keyboard use is temporarily broken - needs an external interrupt, and mega644p has INT0/INT1 shared with UART1!

int dkkeyboard_init(void);
int dkkeyboard_sendcmd( uint8_t c );
void decode(unsigned char sc);
void put_kbbuff( char data );

//--------------------------------------------------------------------
struct _dkdeviceio ps2kbddeviceio =
{
	dkps2kbd_open, 
	dkps2kbd_close, 
	dkps2kbd_read_byte, 
	dkps2kbd_write_byte, 
	dkps2kbd_read_bytes, 
	dkps2kbd_write_bytes, 
	dkps2kbd_seek, 
	dkps2kbd_pos,
	dkps2kbd_eof,
	dkps2kbd_error
};
//--------------------------------------------------------------------
struct _dk_ps2kbd dkps2kbd[1];


//--------------------------------------------------------------------
int dkps2kbd_open( DKFILE *stream )
{
	dkps2kbd[0]._flag = 0;
	
	dkps2kbd[0].rx_buflen = 128;
	dkps2kbd[0].rx_buflimit = 96;

	if( dkps2kbd[0].rx_buffer )
		free( dkps2kbd[0].rx_buffer );					// free previous rx_buffer if it existed
	dkps2kbd[0].rx_buffer = (uint8_t *)malloc( dkps2kbd[0].rx_buflen );
			
	dkps2kbd[0].rx_wr_ptr=0;
	dkps2kbd[0].rx_rd_ptr=0;
			

	stream->info = &dkps2kbd[0];


	if( dkkeyboard_init() )			// dkkeyboard_init() will timeout if no keyboard detected
	{
		_delay_ms(750);				// if no keyboard found, delay and try again (in case of slow powerup)
		if( dkkeyboard_init() )
			return 1;
	}

	sei();

	
	return 0;
}

//--------------------------------------------------------------------
int dkps2kbd_close( void *deviceinfo )
{


	return 0;
}

//--------------------------------------------------------------------
int dkps2kbd_read_byte(void *deviceinfo)
{
	int c;
	
	
	while( ((struct _dk_ps2kbd *)deviceinfo)->rx_wr_ptr == ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr ) 
	{							// wait until character(s) in buffer
		if( io_ctrl_char==3)
			return -1;
	}

	c = (int)((struct _dk_ps2kbd *)deviceinfo)->rx_buffer[ ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr ];
	( ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr )++;

	if( ( ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr ) >= ((struct _dk_ps2kbd *)deviceinfo)->rx_buflen )
		( ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr ) -= ((struct _dk_ps2kbd *)deviceinfo)->rx_buflen;


	return c;
}
//--------------------------------------------------------------------
int dkps2kbd_write_byte(void *deviceinfo, int c)
{
	return c;
}
//--------------------------------------------------------------------
int dkps2kbd_read_bytes(void *deviceinfo, int n, void *ptr)
{
	int i;


	for( i=0; i<n; i++ )
		((uint8_t *)ptr)[i] = dkps2kbd_read_byte(deviceinfo);


	return i;
}
//--------------------------------------------------------------------
int dkps2kbd_write_bytes(void *deviceinfo, int n, void *ptr)
{
	return n;
}
//--------------------------------------------------------------------
int dkps2kbd_seek(void *deviceinfo, off_t offset, int whence)
{
	return 0;
}
//--------------------------------------------------------------------
fpos_t dkps2kbd_pos(void *deviceinfo)
{		// for keyboard, pos() returns # of bytes waiting in rx buffer
	if( ((struct _dk_ps2kbd *)deviceinfo)->rx_wr_ptr == ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr )
		return 0;
	else if( ((struct _dk_ps2kbd *)deviceinfo)->rx_wr_ptr > ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr )
		return ((struct _dk_ps2kbd *)deviceinfo)->rx_wr_ptr - ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr;
	else
		return ((struct _dk_ps2kbd *)deviceinfo)->rx_buflen - ( ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr - ((struct _dk_ps2kbd *)deviceinfo)->rx_wr_ptr );

	return 0;
}
//--------------------------------------------------------------------
int dkps2kbd_eof(void *deviceinfo)
{
	if( ((struct _dk_ps2kbd *)deviceinfo)->rx_wr_ptr != ((struct _dk_ps2kbd *)deviceinfo)->rx_rd_ptr ) 
		return 0;

	return -1;
}
//--------------------------------------------------------------------
int dkps2kbd_error(void *deviceinfo)
{
	return 0;
}

//--------------------------------------------------------------------

ISR(INT0_vect) 
{
	static uint8_t data=0;
	static uint8_t edge=0;
	static uint8_t bitcount=11;



	if( !edge )
	{
		if( bitcount<11 && bitcount>2 )				// bit 3 to 10 is data - parity, stop, and start bits ignored
		{
			data >>= 1;
			if( PIND & (1<<1) )
				data |= 0x80;
		}
		EICRA |= (1<<ISC01)||(1<<ISC00);			// int0 on rising edge
	}
	else
	{
		bitcount--;
		if( !bitcount )
		{											// all bits received
			bitcount = 11;
			decode( data );
		}
		EICRA &= ~((1<<ISC01)||(1<<ISC00));			// int0 on falling edge
	}

	edge = !edge;
}



//--------------------------------------------------------------------
// int dkkeyboard_init(void)
// return true if a keyboard was initialized
// note: keyboard may need some time (a few hundred ms?) from power-on before initialization is possible
int dkkeyboard_init(void)
{
	uint8_t i;


	DDRD &= ~0x03;
	PORTD |= 0x03;						// set pd0/pd1 as inputs, pullups on

	EICRA &= ~((1<<ISC01)||(1<<ISC00));			// int0 on falling edge

	
	if( (i=dkkeyboard_sendcmd( 0xFF )) )
		return i;

	EIMSK |= (1<<INT0);
										// wait 500-750ms for 0xAA (BAT successful)
	for( i=0; i<200; i++ )				// loop until up to 200 * 10ms (2 seconds)
	{
		if( !dkps2kbd_eof(&dkps2kbd[0]) )
		{
			if( dkps2kbd_read_byte(&dkps2kbd[0]) == DKKEY_BATOK )
				break;
		}
		_delay_ms( 10 );
	}
	if( !(i<200) )
		return 1;

	return 0;
}


//--------------------------------------------------------------------

int dkkeyboard_sendcmd( uint8_t c )
{
	uint8_t i,n;
	uint8_t parity=0;


	EIMSK &= ~(1<<INT0);			// disable interrupt on falling edge of keyboard clk

//1)   Bring the Clock line low for at least 100 microseconds.
	DDRD |= (1<<0);					// set PD0 as output
	PORTD &= ~(1<<0);				// set PD0 low
	_delay_us( 150 );
//2)   Bring the Data line low.
	DDRD |= (1<<1);					// set PD1 as output
	PORTD &= ~(1<<1);				// set PD1 low
//3)   Release the Clock line.
	DDRD &= ~(1<<0);				// set PD0 as input
	PORTD |= (1<<0);				// enable PD0 pullup

	for( i=0; i<8; i++ )
	{
//4)   Wait for the device to bring the Clock line low.
//		while( PIND & (1<<0) )
//			;
		for( n=0; n<DKKEYCLKTIMEOUT; n++ )
		{
			if( !(PIND & (1<<0)) )
				break;
			_delay_us( 10 );
		}
		if(!(n<DKKEYCLKTIMEOUT))
			return 10+i;				// error - timeout waiting for clk
//5)   Set/reset the Data line to send the first data bit
//		if( c&(1<<(7-i)) )
		if( c&(1<<i) )
		{
			PORTD |= (1<<1);
			parity++;
		}
		else
			PORTD &= ~(1<<1);
//6)   Wait for the device to bring Clock high.
//		while( !(PIND & (1<<0)) )
//			;
		for( n=0; n<DKKEYCLKTIMEOUT; n++ )
		{
			if( PIND & (1<<0) )
				break;
			_delay_us( 10 );
		}
		if(!(n<DKKEYCLKTIMEOUT))
			return 20+i;				// error - timeout waiting for clk
//8)   Repeat steps 4-6 for the other seven data bits and the parity bit
	}


//4)   Wait for the device to bring the Clock line low.
//	while( PIND & (1<<0) )
//		;
	for( n=0; n<DKKEYCLKTIMEOUT; n++ )
	{
		if( !(PIND & (1<<0)) )
			break;
		_delay_us( 10 );
	}
	if(!(n<DKKEYCLKTIMEOUT))
		return 30;				// error - timeout waiting for clk
// send parity bit
	if( parity & 1 )
		PORTD &= ~(1<<1);
	else
		PORTD |= (1<<1);
//6)   Wait for the device to bring Clock high.
//	while( !(PIND & (1<<0)) )
//		;
	for( n=0; n<DKKEYCLKTIMEOUT; n++ )
	{
		if( PIND & (1<<0) )
			break;
		_delay_us( 10 );
	}
	if(!(n<DKKEYCLKTIMEOUT))
		return 31;				// error - timeout waiting for clk
////4)   Wait for the device to bring the Clock line low.
//	while( PIND & (1<<0) )
//		;
	for( n=0; n<DKKEYCLKTIMEOUT; n++ )
	{
		if( !(PIND & (1<<0)) )
			break;
		_delay_us( 10 );
	}
	if(!(n<DKKEYCLKTIMEOUT))
		return 32;				// error - timeout waiting for clk

	

//9)   Release the Data line.
	DDRD &= ~(1<<1);				// set PD1 as input
	PORTD |= (1<<1);				// enable PD1 pullup

//10) Wait for the device to bring Data low.
//	while( PIND & (1<<1) )
//		;
	for( n=0; n<DKKEYCLKTIMEOUT; n++ )
	{
		if( !(PIND & (1<<1)) )
			break;
		_delay_us( 10 );
	}
	if(!(n<DKKEYCLKTIMEOUT))
		return 33;				// error - timeout waiting for clk

//11) Wait for the device to bring Clock  low.
//	while( PIND & (1<<0) )
//		;
	for( n=0; n<DKKEYCLKTIMEOUT; n++ )
	{
		if( !(PIND & (1<<0)) )
			break;
		_delay_us( 10 );
	}
	if(!(n<DKKEYCLKTIMEOUT))
		return 34;				// error - timeout waiting for clk

//12) Wait for the device to release Data and Clock
//	while( ( !(PIND & (1<<0)) ) || ( !(PIND & (1<<1)) ) )
//		;
	for( n=0; n<DKKEYCLKTIMEOUT; n++ )
	{
		if( (PIND & (1<<0)) && (PIND & (1<<1)) )
			break;
		_delay_us( 10 );
	}
	if(!(n<DKKEYCLKTIMEOUT))
		return 35;				// error - timeout waiting for clk and data to be released

	EIMSK = (1<<INT0);				// enable interrupt on falling edge of keyboard clk


	return 0;
}


//--------------------------------------------------------------------
// decode() from app note AVR313 
void decode(unsigned char sc)
{
    static unsigned char is_up=0, shift=0, ctrl=0, alt=0, gui=0, caps=0, scroll=0, num=0;
	static unsigned char is_E0=0;
	static unsigned char is_92=0;
    unsigned char i;
	char str[32];	//temp


    if (!is_up)                // Last data received was the up-key identifier
    {
		if( sc==0xF0 )
		{	       // The up-key identifier
			is_E0 = 0;
			is_92 = 0;
            is_up = 1;	        return;
		}

		if( sc==0xE0 || sc==0xE1 )
		{						// check for extended keys
			is_E0 = 1;
			return;
		}

		if( is_E0 )
			sc |= 0x80;		// set high bit of scancode if we received E0 previously (extended scancode)


		if( sc == 0xFC )			// prtscr
		{
			if( is_92 )
				sc = 0xF8;			// set key to printscreen
		}

		if( sc == 0x77 )			// numlock/pause
		{
			if( is_92 )
				sc = 0xF7;			// note: dkkey_pause must be at 0xF7 in table for this to be ok
		}


        switch (sc)
		{
         	case 0x11:        // Left alt
				alt |= 1;
				break;

          	case 0x91:        // Right alt
            	alt |= 2;
				is_E0 = 0;
	            break;

			case 0x92:
				is_92 = 1;		// numlock is down and ins/del/home/end/pgup/pgdn/arrows pressed or prtscr/ctrl-sysrq
				break;

         	case 0x12 :        // Left SHIFT
				shift |= 1;
				break;

          	case 0x59 :        // Right SHIFT
            	shift |= 2;
	            break;

			case 0x14:			// left ctrl (right-ctrl = E0 14)
				ctrl |= 1;
				break;

			case 0x94:			// right ctrl
			is_92 = 1;		// numlock is down and ins/del/home/end/pgup/pgdn/arrows pressed or prtscr/ctrl-sysrq
    	        ctrl |= 2;
				is_E0 = 0;
    	        break;

			case 0x9F:			// LGUI
				gui |= 1;
				is_E0 = 0;
				break;

			case 0xA7:			// RGUI
    	        gui |= 2;
				is_E0 = 0;
    	        break;

			case 0x58:			// capslock
    	        caps = !caps;
				dkkeyboard_sendcmd( 0xED );
				dkkeyboard_sendcmd( (caps?(1<<2):0)|(num?(1<<1):0)|(scroll?(1<<0):0) );
    	        break;

			case DKKEY_SCROLL:		// scrolllock
    	        scroll = !scroll;
				dkkeyboard_sendcmd( 0xED );
				dkkeyboard_sendcmd( (caps?(1<<2):0)|(num?(1<<1):0)|(scroll?(1<<0):0) );
    	        break;

			case DKKEY_NUMLOCK:		// numlock
    	        num = !num;
				dkkeyboard_sendcmd( 0xED );
				dkkeyboard_sendcmd( (caps?(1<<2):0)|(num?(1<<1):0)|(scroll?(1<<0):0) );
    	        break;


          default:
			if( shift )		// If shift pressed, check for key in shift table
			{                    
                for(i = 0; shifted[i][0]!=sc && shifted[i][0]; i++);
                if (shifted[i][0] == sc)			// if key was found in shifted table, use that code
				{
					if( ctrl && shifted[i][1]=='C' )				// ctrl-c has been received
						io_ctrl_char = DKKEY_BREAK;

					if( ctrl && isalpha( shifted[i][1] ) )
						put_kbbuff( shifted[i][1]-'A'+1 );		// put ctrl-alphachar in buffer
					else if( caps && isalpha( shifted[i][1] ) )
						put_kbbuff( tolower( shifted[i][1] ) );
					else
	                    put_kbbuff(shifted[i][1]);

					return;
				}
            }
						// shift is not pressed, or the key was not found in shifted table - find key in unshifted table
            for(i = 0; unshifted[i][0]!=sc && unshifted[i][0]; i++);
            if (unshifted[i][0] == sc)
			{
				if( unshifted[i][1]>=DKKEY_NUM_PER && unshifted[i][1]<=DKKEY_NUM_9 )
				{
					if( num )
					{			// numlock is on
						put_kbbuff( unshifted[i][1]-DKKEY_NUM_PER+'.' );
						break;
					}
					else 
					{			// numlock is off
						put_kbbuff( unshifted[i][1]-DKKEY_NUM_PER+DKKEY_DEL );
						break;
					}
				}

				if( ( ctrl && unshifted[i][1]=='c' ) || unshifted[i][1]==DKKEY_BREAK )	// ctrl-c has been received
					io_ctrl_char = DKKEY_BREAK;			// break signal (ctrl-c) was received
				else if( unshifted[i][1]==DKKEY_PAUSE )
				{
					if( io_ctrl_char != DKKEY_PAUSE )
						io_ctrl_char = DKKEY_PAUSE;				// pause key was pressed
					else
						io_ctrl_char = 0;
					break;
				}
				else if( unshifted[i][1]==DKKEY_DEL && ctrl && alt )
					;	//	dkreset();

				if( isalpha( unshifted[i][1] ) && ctrl )
					put_kbbuff( unshifted[i][1]-'a'+1 );		// put ctrl-alphachar in buffer
				else if( caps && isalpha( unshifted[i][1] ) )
					put_kbbuff( toupper( unshifted[i][1] ) );
				else
                    put_kbbuff(unshifted[i][1]);
			}
			else			// DEBUG: display scancode received - it was not found in translation table
			{						// got a key not in the translation table
				sprintf( str, "%02X", sc );
				put_kbbuff( str[0] );
				put_kbbuff( str[1] );
			}

	        break;
        }
    } else {				// handle key-up events
        is_up = 0;                            // Two 0xF0 in a row not allowed
        switch (sc)
        {
         	case 0x11:        // Left alt
				alt &= ~1;
				break;

          	case 0x91:        // Right alt
				alt &= ~2;
	            break;

          case 0x12:                        // Left SHIFT
				shift &= ~1;
				break;
          case 0x59:                        // Right SHIFT
	            shift &= ~2;
	            break;

          case 0x14:							// left ctrl (right ctrl=E014)
				ctrl &= ~1;
				break;

          case 0x94:							// right ctrl
			ctrl &= ~2;
            break;

			case 0x9F:			// LGUI
				gui &= ~1;
				break;

			case 0xA7:			// RGUI
    	        gui &= ~2;
    	        break;

        } 
    }    
} 

//--------------------------------------------------------------------

void put_kbbuff( char data )
{
	if( dkps2kbd[0].rx_buffer )				// if rx_buffer isn't allocated, the received byte will be lost
	{
		dkps2kbd[0].rx_buffer[ dkps2kbd[0].rx_wr_ptr ] = data;

		dkps2kbd[0].rx_wr_ptr++;
		if( dkps2kbd[0].rx_wr_ptr >= dkps2kbd[0].rx_buflen )
			dkps2kbd[0].rx_wr_ptr -= dkps2kbd[0].rx_buflen;
	}
}

//--------------------------------------------------------------------

